<?php

Raise::load('core.RaiseCollection');
Raise::load('core.IRaiseTypedDataStructure');
Raise::load('core.RaiseVariable');
Raise::load('core.RaiseKeyValuePair');

/**
 * RaiseTypedCollection class
 * represents a RaiseCollection that contains items of a certain item type
 *
 * @author Sam-Mauris Yong <hellclanner at live dot com>
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package Raise.Core.Collection
 * @since 1.2
 */
class RaiseTypedCollection extends RaiseCollection implements IRaiseTypedDataStructure {

    /**
     * The type of item to store in the collection
     * @var string
     */
    private $type;

    /**
     * Create a new RaiseTypedCollection
     * @param string $type The class name or data type that the collection accepts
     * @param RaiseCollection $a (optional)  The array or RaiseCollection to create from
     */
    function __construct($type, $a = null) {
        $c = func_num_args();
        if($c > 1){
            $a = func_get_args();
            array_shift($a);

            if($c == 2 && $a[0] instanceof RaiseTypedCollection){
                if($a[0]->type != $this->type){
                    throw new RaiseInvalidArgumentException('RaiseTypedCollection expecting incoming RaiseTypedCollection to have the same type. Type \'' . $a[0]->type . '\' given. expecting type \'' . $this->type . '\'.');
                }
            }elseif($c == 2 && $a[0] instanceof RaiseCollection){
                foreach($a[0]->_a as $i){
                    $this->validateValues($i);
                }
            }elseif($c == 2 && RaiseVariable::isType($a[0], RaiseVariable::TYPEARRAY)){
                foreach($a[0] as $i){
                    $this->validateValues($i);
                }
            }else{
                $this->validateValues($a);
            }

            parent::__construct($a);
        }
        $this->type = $type;
    }

     /**
     * Get the data type of items that the collection accepts
     * @return string
     */
    public function type(){
        return $this->type;
    }

    /**
     * Add a Item of a certain type
     */
    public function add() {
        $a = func_get_args();
        if(count($a) == 2)
        {
            $this->validateValues($a[1]);
            parent::add($a[0], $a[1]);
        }
        elseif(count($a) == 1)
        {
            if($a[0] instanceof RaiseKeyValuePair){
                $this->validateValues($a[0]->value());
                parent::add($a[0]);
            }else{
                $this->validateValues($a[0]);
                parent::add($a[0]);
            }
        }
        else
        {
            throw new RaiseInvalidArgumentException('The argument count is invalid. Should be add($key, $value) or add($value)');
        }
    }

    /**
     * Validate the value and throw the appropriate exception if an error is detected
     * @param mixed $o
     */
    private function validateValues($o){
        if(RaiseVariable::isType($o, $this->type) || RaiseVariable::isInstanceOf($o, $this->type)){
            return;
        }
        throw new RaiseInvalidArgumentException('RaiseTypedCollection expecting new value to have type of \'' . $this->type . '\'. ' . RaiseVariable::typeInfo($o) . ' was given.');
    }

    /**
     * Append another RaiseTypedCollection to this RaiseTypedCollection
     * @param RaiseTypedCollection $a
     */
    public function append($a) {
        if(!($a instanceof RaiseTypedCollection) || $a->type != $this->type){
            throw new RaiseInvalidArgumentException('Append operation expecting RaiseTypedCollection of the same type to be the input.');
        }
        parent::append($a);
    }


    /**
     * Prepend another RaiseTypedCollection to this RaiseTypedCollection
     * @param RaiseTypedCollection $a
     */
    public function prepend($a) {
        if(!($a instanceof RaiseTypedCollection) || $a->type != $this->type){
            throw new RaiseInvalidArgumentException('Prepend operation expecting RaiseTypedCollection of the same type to be the input.');
        }
        parent::prepend($a);
    }

}
